package libce

import (
	"log"
	"reflect"
	"sync"
	"time"

	"github.com/go-redis/redis"
)

type Redis struct {
	client  *redis.Client
	prefix  string
	hSetKey string
	mu      sync.RWMutex
}

func (hc *Redis) HSet(k, field string, x interface{}) error {
	k = hc.preKey(k)
	hc.mu.Lock()
	err := hc.client.HSet(k, field, x).Err()
	if err != nil {
		hc.mu.Unlock()
		return err
	}
	hc.mu.Unlock()
	return nil
}

func (hc *Redis) HGet(k, field string) (value interface{}, has bool) {
	k = hc.preKey(k)
	hc.mu.RLock()
	value = hc.client.HGet(k, field).Val()
	if value == "" {
		hc.mu.RUnlock()
		return
	}
	has = true
	hc.mu.RUnlock()
	return
}

func (hc *Redis) HDel(k string, field ...string) {
	hc.mu.Lock()
	hc.client.HDel(k, field...)
	hc.mu.Unlock()
}

func (hc *Redis) Client() *redis.Client {
	return hc.client
}

func (hc *Redis) preKey(k string) string {
	if hc.prefix != "" {
		k = hc.prefix + ":" + k
	}
	return k
}

//Set redis string type set
func (hc *Redis) Set(k string, x interface{}, d time.Duration) {
	hc.mu.Lock()
	hc.client.Set(hc.preKey(k), ToStr(x), d)
	hc.mu.Unlock()
}

//Get return string type value
func (hc *Redis) Get(k string) (value interface{}, has bool) {
	k = hc.preKey(k)
	hc.mu.RLock()
	value = hc.client.Get(k).Val()
	if value == "" {
		hc.mu.RUnlock()
		return
	}
	has = true
	hc.mu.RUnlock()
	return
}

func (hc *Redis) Increment(k string, n int64) error {
	k = hc.preKey(k)
	hc.mu.Lock()
	err := hc.client.IncrByFloat(k, float64(n)).Err()
	if err != nil {
		hc.mu.Unlock()
		return err
	}
	hc.mu.Unlock()
	return nil
}

func (hc *Redis) Decrement(k string, n int64) error {
	k = hc.preKey(k)
	hc.mu.Lock()
	err := hc.client.IncrByFloat(k, float64(n*-1)).Err()
	if err != nil {
		hc.mu.Unlock()
		return err
	}
	hc.mu.Unlock()
	return nil
}

func (hc *Redis) Delete(k string) {
	k = hc.preKey(k)
	hc.mu.Lock()
	hc.client.Del(k)
	hc.mu.Unlock()
}

func (hc *Redis) Flush() {
	hc.mu.Lock()
	hc.client.FlushDBAsync()
	hc.mu.Unlock()
}

func (hc *Redis) RememberWarp(rememberFunc RememberFunc, op RememberOption) RememberFunc {
	return func() (value interface{}, err error) {
		val, has := hc.Get(op.Key)
		if has {
			typ := reflect.TypeOf(op.BindBean)
			if typ.Kind() == reflect.Ptr {
				err = StrTo(val.(string)).Convert(op.BindBean)
			} else {
				err = StrTo(val.(string)).Convert(&op.BindBean)
			}
			value = op.BindBean
			return
		}
		if value, err = rememberFunc(); err != nil {
			return
		}
		hc.Set(op.Key, value, op.Expire)
		return
	}
}

/*
	{
		"Network": "",
		"Addr": "",
		"Password": "",
		"DB": 11
	}
*/

// NewRedis sdn is a json string,args[0] = prefix,args[1] = hSetKey
func NewRedis(sdn string, args ...string) (redisClient *Redis) {
	var err error
	defer func() {
		if err != nil {
			panic(err)
		}
	}()
	op := new(redis.Options)
	if err = json.UnmarshalFromString(sdn, op); err != nil {
		return
	}
	c := redis.NewClient(op)
	_, err = c.Ping().Result()
	if err != nil {
		log.Fatalln("Redis 服务器连接失败")
		return
	}
	inArgs := make([]string, 2, 2)
	copy(inArgs, args)
	redisClient = &Redis{
		client:  c,
		prefix:  inArgs[0],
		hSetKey: inArgs[1],
	}
	return
}
